﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Media;
using System.Net;
using System.Text.RegularExpressions;
using LumiSoft.Net;
using LumiSoft.Net.Media;
using LumiSoft.Net.SIP.Stack;
using TEL.ClientClasses.Private;
using TEL.ClientClasses.Public;
using TEL.ClientClasses.Resources;
using TEL.ClientClasses.SipInterfaces;
using TEL.WinMediaWPF.Audio.Wav;
using TEL.WinMediaWPF.DTMF;


namespace TEL.ClientClasses
{
    public class SipOperation : IDisposable
    {
        private SIP_Stack m_pStack;
        public int StackState
        {
            get
            {
                if (m_pStack == null) return -1;
                    return (int) m_pStack.State;
            }
        }
        public static Account MainAccount;
        private int m_RtpBaseAudioPort = 31000;
        private int m_RtpBaseVIdioPort = 31100;
        private List<SipLine> m_lines;
        public SimplePhoneLog SimpleLog;
        private ISipAccount m_property;
        public ISipAccount Property
        {
            get { return m_property; }
            private set { m_property = value; }
        }

        public static void TestSignalVolume(int volume)
        {
            WavePlayer m_pAudioPlayer = new WavePlayer(AudioOutStram);
            m_pAudioPlayer.Valume = volume;
            m_pAudioPlayer.Play(ResManager.GetStream("ringing.wav"), 20);

            System.Threading.Thread.Sleep(300);

            m_pAudioPlayer.Stop();
            
        }
        public IOptions Options { get; private set; }

        public void SignalVolumeToLines()
        {
            m_lines.ForEach(x =>x.SignalVolume = Options.SignalVolume);
        }
        private int? m_ResponseStatusCode = null;
        public int? ResponseStatusCode
        {
            get { return m_ResponseStatusCode; }
            private set
            {
                m_ResponseStatusCode = value;
                string str = m_ResponseStatusCode.HasValue
                                 ? new SIP_ResponseCode().GetMessage(value.Value)
                                 : string.Empty;
                if (ResponseStatusEvent != null) ResponseStatusEvent(this, new SipResponseEventArgs(str, value));
            }
        }
        
        public event EventHandler<RegistrationEventArgs> RegisteringEvent;
        public event EventHandler<SipLinesEventArgs> SipLinesCreateEvent;
        public event EventHandler<StrErrorEventArgs> TelSipError;
        public event EventHandler<SipResponseEventArgs> ResponseStatusEvent;

        public event EventHandler<RegistrationEventArgs> RegisteredEvent;
        public event EventHandler<RegistrationEventArgs> UnRegisteredEvent;

        public static Stream generalTone(char simvol)
        {
            short FRAME_SIZE = 160;
            #region генератор тона
            short[] shortsArray = new short[FRAME_SIZE];
            DtmfGenerator dtmfGenerator = new DtmfGenerator(FRAME_SIZE, 40, 20);

            char[] dialButtons = new char[DtmfGenerator.NUMBER_BUTTONS];
            dialButtons[0] = simvol;

            dtmfGenerator.transmitNewDialButtonsArray(dialButtons, DtmfGenerator.NUMBER_BUTTONS);

            MemoryStream stream = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(stream);

            while (!dtmfGenerator.getReadyFlag())
            {
                // 8 kHz, 16 bit's PCM frame's generation
                dtmfGenerator.dtmfGenerating(shortsArray);

                for (int i = 0; i < shortsArray.Length; i++)
                    writer.Write(shortsArray[i]);

            }
            #endregion

            return stream;
        }

        private static int m_pOutVolume = 100;
        public static int OutVolume
        {
            get { return m_pOutVolume; }

        }

        public void SetOutVolume(int value)
        {
            m_pOutVolume = value;
            m_lines.ForEach(x=>x.SetVolume(value));
        }

        public void SetConference(bool value)
        {
            m_lines.ForEach(x=>x.Isconference = value);
        }

        public void SetFocusToLines (SipLine sipLine)
        {
            if(sipLine != null)
            {
                List<SipLine> lines = (from q in m_lines where q.LineNumber != sipLine.LineNumber select q).ToList();
                lines.ForEach(x => x.SetFocus(false));
                sipLine.SetFocus(true);
            }
            else
            {
                m_lines.ForEach(x => x.SetFocus(false));
            }
        }

        private static AudioOutDevice m_audioOutStram = null;
        public static AudioOutDevice AudioOutStram
        {
            get
            {
                return m_audioOutStram;
            }
            //   set { m_audioOutStram = value; }
        }

        private static AudioInDevice m_audioInStream = null;
        public static AudioInDevice AudioInStream
        {
            get { return m_audioInStream; }
            //   set { m_audioInStream = value; }
        }

        private IPAddress m_ConnectorIP = null;

        private bool m_IsCall;
        public bool IsCall
        { get { return m_IsCall; } }

        public void AddToneDTMF(SipLine sipLine, ClickNumberEventArgs button)
        {
            try
            {
                if (sipLine == null) throw new ArgumentNullException("SipLine");
                Stream stream = generalTone(button.NumberCaption);
                byte[] tone = new byte[stream.Length];
                stream.Position = 0;
                stream.Read(tone, 0, tone.Length);

                WAVFileMemory m_pWavFile = new WAVFileMemory();
                stream.Position = 0;
                m_pWavFile.Create("", false, 8000, 16);
                m_pWavFile.AddSample_ByteArray(stream);

                m_pWavFile.Close();
                m_pWavFile.mStream.Position = 0;
                SoundPlayer soundPlayer = new SoundPlayer();
                soundPlayer.Stream = m_pWavFile.mStream;
                soundPlayer.Play();
                soundPlayer.Dispose();

                if (sipLine == null) return;
                 sipLine.TonePlay(button, stream);
            }
            catch (Exception error)
            { if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation AddToneDTMF", error, false)); }
        }

        public SipOperation(ISipAccount property, IOptions options)
        {
            SimpleLog = new SimplePhoneLog();
            SimpleLog.Write(LogType.Info, this.GetType().ToString(), "Start");
            Options = options;
            Property = property;
            m_pStack = new SIP_Stack();
            m_pStack.UserAgent = "TEL SIP 1.0";

           m_lines = new List<SipLine>();
            if (AudioOutCkek())
                m_audioOutStram = (from q in AudioOut.Devices where q.Index == Options.AudioOut select q).First();
            if (AudioInCkek())
                m_audioInStream = (from q in AudioIn.Devices where q.Index == Options.AudioIn select q).First();
        }

        private bool m_generatedLines = false;
        public void CreateLines(int linesCount)
        {
            if (m_generatedLines) return;
            try
            {
                if (m_pStack == null) throw new ArgumentNullException("SIP_Stack");
                if (m_ConnectorIP == null) throw new ArgumentNullException("m_ConnectorIP");

                for (int i = 0; i < linesCount; i++)
                {
                    m_lines.Add(new SipLine(i + 1, m_ConnectorIP, m_pStack, Property, m_RtpBaseAudioPort) { SignalVolume = Options.SignalVolume, Log = SimpleLog});
                }
                m_generatedLines = true;
                if (SipLinesCreateEvent != null) SipLinesCreateEvent(this, new SipLinesEventArgs(m_lines));
            }
            catch (Exception error)
            { if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation CreateLines", error, false)); }
        }

        /// <summary>
        /// Возвращает первую свободную линию из списка созданых линий
        /// </summary>
        /// <returns></returns>
        public SipLine GetLineFree()
        {
            SipLine res = null;
            try
            {
                res = (from q in m_lines where q.State == CallState.Entity select q).Last();
            }
            catch (Exception error)
            {
                if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation GetLineFree", error, false));
            }
            return res;
        }

        /// <summary>
        ///  Возвращает линию занатую определнным номером
        /// </summary>
        /// <param name="lineCall">номер удаленного <b>sipuri</b> свойство <b>LineCall</b> класса <b>Call</b> </param>
        /// <returns></returns>
        public SipLine GetLineByLineCall(string lineCall)
        {
            SipLine res = null;
            try
            {
                var r = from q in m_lines where q.IsHasCall && q.LineCall == lineCall select q;
                if (r.Count() > 0)
                    res = r.First();
            }
            catch (Exception error)
            {
                if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation GetLineByLineCall", error, false));
            }
            return res;
        }

        public void KillCallAll()
        {
            (from q in m_lines where q.IsHasCall select q).ToList().ForEach(x =>
            {
                if (x.State == CallState.Hold)
                {
                    x.ToggleCallOnHold();
                }
                x.CallTerminated();
            });

        }

        #region Implementation of IDisposable

        private bool mIsDispose = false;

        public void Dispose()
        {
            if (mIsDispose) return;
            mIsDispose = true;
            if (Connected ) DisConnection();
            else
            {
                 m_pStack.Dispose();
                
            } 
        }

        #endregion

        /// <summary>
        /// закрывает соединения с sip сервером
        /// </summary>
        private void DisConnection()
        {
            if (m_pStack.State == SIP_StackState.Started)
                try
                {
                    KillCallAll();
                    m_pStack.Stop();

                    m_lines.Clear();
                    if (SipLinesCreateEvent != null) SipLinesCreateEvent(this, new SipLinesEventArgs(m_lines));

                }
                catch (Exception error)
                {
                    if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation DisConnection", error, false));
                }
        }
        public bool Connected { get; private set; }

        /// <summary>
        /// поднимает соединения с sip сервером
        /// </summary>
        public void Connection()
        {
            try
            {
                SipSetting ds = new SipSetting(this.Property) { Register = true };
                m_pStack.BindInfo = ds.GetIPBindInfo().ToArray();
                m_pStack.RequestReceived += new EventHandler<SIP_RequestReceivedEventArgs>(m_pStack_RequestReceived);
                m_pStack.Logger.WriteLog += new EventHandler<LumiSoft.Net.Log.WriteLogEventArgs>(Logger_WriteLog);
                m_pStack.Error += new EventHandler<ExceptionEventArgs>(m_pStack_Error);
                m_pStack.Credentials.AddRange(ds.GetNetworkCredential().ToArray());
                m_pStack.Start();

                MainAccount = ds.GetAccount();

                if (MainAccount.Register)
                {
                    SIP_Uri registrarServer = SIP_Uri.Parse(MainAccount.RegistrarServer);

                    SIP_UA_Registration registration = m_pStack.CreateRegistration(
                        registrarServer,
                        MainAccount.AOR,
                        AbsoluteUri.Parse(registrarServer.Scheme + ":" + MainAccount.AOR.Split('@')[0] + "@auto-allocate"),
                        MainAccount.RegisterInterval
                        );
                    registration.Error += new EventHandler<SIP_ResponseReceivedEventArgs>(registration_Error);
                    registration.StateChanged += registration_StateChanged;
                    registration.Registered += new EventHandler(registration_Registered);
                    registration.Unregistered += new EventHandler(registration_Unregistered);
                    
                    registration.BeginRegister(true);
                   
                }
            }
            catch (Exception error)
            {
                if (this.RegisteringEvent != null)
                    this.RegisteringEvent.Invoke(this,
                                                 new RegistrationEventArgs(RegistrationState.DefaultNull, "Разъединен"));
                m_pStack.Dispose();
               

                if (TelSipError != null)
                    TelSipError(this, new StrErrorEventArgs("SipOperation Connection", error, false));
            }
        }

        void registration_Unregistered(object sender, EventArgs e)
        {
            try
            {
                SIP_UA_Registration registration = (SIP_UA_Registration)sender;
                if (this.UnRegisteredEvent != null) UnRegisteredEvent(sender, createRegistrationEventArgs(registration));
            }
            catch (Exception)
            {
            }
        }

        void registration_Registered(object sender, EventArgs e)
        {
            try
            {
                SIP_UA_Registration registration = (SIP_UA_Registration)sender;
                if (this.RegisteredEvent != null) RegisteredEvent(sender, createRegistrationEventArgs(registration));
            }
            catch (Exception)
            {
            }
           
        }

        void registration_Error(object sender, SIP_ResponseReceivedEventArgs e)
        {
            Exception exception = new Exception(e.Response.ToString());
            ResponseStatusCode = e.Response.StatusCode;
            if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation registration_Error", exception, false));
        }

        /// <summary>
        /// Обработка ошибок обращения с сервером
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void m_pStack_Error(object sender, ExceptionEventArgs e)
        {
            if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation m_pStack_Error", e.Exception, false));
        }

        void Logger_WriteLog(object sender, LumiSoft.Net.Log.WriteLogEventArgs e)
        {
            
            if (System.Text.RegularExpressions.Regex.IsMatch(e.LogEntry.Text, "^(Request|Response)", RegexOptions.IgnoreCase))
            if (SimpleLog!= null) SimpleLog.Write(LogType.Message, "--StackLog--", e.LogEntry.Text);
        }

        public event EventHandler<BoolEventArgs> DNDCanged;
        private bool m_dnd = false;
        public bool DND
        {
            get { return m_dnd; }
            set
            {
                if (m_dnd != value)
                {
                    m_dnd = value;
                    if (DNDCanged != null) DNDCanged(this, new BoolEventArgs(value));
                }
            }
        }

        private static bool m_microPhobeActive = true;
        public static bool MicroPhobeActive
        {
            get { return m_microPhobeActive; }

        }

        public void SetMicroPhobeActive(bool value)
        {
            m_microPhobeActive = value;
            MicrophoneActiveToCall(m_microPhobeActive);
        }

        private void MicrophoneActiveToCall(bool value)
        {
            (from q in m_lines where q.IsHasCall select q).ToList().ForEach(x => x.MicrophoneActiveToCall(value));
        }

        public void TrminateAfterConfirence()
        {
            try
            {
                List<SipLine> lines =
                    (from q in m_lines where q.IsHasCall && q.State == CallState.Active select q).ToList();
                if (lines != null && lines.Count > 0)
                    lines.ForEach(x => x.CallTerminated());
            }
            catch (Exception error)
            { if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation TrminateAfterConfirence", error, false)); }
        }

            private BoolEventArgs m_lInvite;
            private BoolEventArgs m_lCancel;
            private BoolEventArgs m_lBye;

        void m_pStack_RequestReceived(object sender, SIP_RequestReceivedEventArgs e)
        {
            switch (e.Request.RequestLine.Method)
            {
                #region INVITE

                case SIP_Methods.INVITE:
                    if (GetLineByLineCall(e.Request.From.Address.Uri.ToString()) != null)
                        return;
                    m_lInvite = new BoolEventArgs(true);
                    lock (m_lInvite)
                    {
                        try
                        {
                            SipLine sipLine = GetLineFree();
                            if (sipLine == null || DND)
                            {
                                Call call = new Call(m_pStack, e.ServerTransaction);
                                call.Reject(SIP_ResponseCodes.x486_Busy_Here);
                               // call.Terminate();
                                return;
                            }

                            if (sipLine != null) sipLine.Invite(e);
                        }
                        catch (Exception error)
                        {
                            if (TelSipError != null)
                                TelSipError(this,
                                            new StrErrorEventArgs("SipOperation RequestReceived (invite)", error, false));
                        }
                        m_lInvite = null;
                    }
                    break;

                #endregion

                #region Cancel

                case SIP_Methods.CANCEL:
                    m_lCancel = new BoolEventArgs(true);
                    lock (m_lCancel)
                    {
                        try
                        {
                            SIP_ServerTransaction trToCancel =
                                m_pStack.TransactionLayer.MatchCancelToTransaction(e.Request);
                            if (trToCancel != null)
                            {
                                trToCancel.Cancel();
                                e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok,
                                                                                         e.Request));
                            }
                            else
                            {
                                e.ServerTransaction.SendResponse(
                                    m_pStack.CreateResponse(SIP_ResponseCodes.x481_Call_Transaction_Does_Not_Exist,
                                                            e.Request));
                            }
                        }
                        catch (Exception error)
                        {
                            if (TelSipError != null)
                                TelSipError(this,
                                            new StrErrorEventArgs("SipOperation RequestReceived (cancel)", error, false));
                        }
                    }
                    m_lCancel = null;
                    break;

                #endregion

                #region MESSAGE

                case SIP_Methods.MESSAGE:
                    {
                    }
                    break;

                #endregion

                #region BYE

                case SIP_Methods.BYE:
                    m_lBye = new BoolEventArgs(true);
                    lock (m_lBye)
                    {
                        try
                        {
                            System.Diagnostics.Debug.WriteLine("Поймал bye");
                            SIP_Dialog dialog = m_pStack.TransactionLayer.MatchDialog(e.Request);
                            if (dialog != null)
                            {
                                e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok,
                                                                                         e.Request));
                                dialog.Terminate();
                            }
                            else
                            {
                                e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok,
                                                                                         e.Request));
                            }
                        }
                        catch (Exception error)
                        {
                            if (TelSipError != null)
                                TelSipError(this,
                                            new StrErrorEventArgs("SipOperation RequestReceived (bye)", error, false));
                        }
                    }
                    m_lBye = null;
                    break;
                #endregion

                #region
                case SIP_Methods.REGISTER:
                    break;
                #endregion

                default:
                    if (e.Request.RequestLine.Method != SIP_Methods.ACK)
                    {
                        e.ServerTransaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x501_Not_Implemented,
                                                                                 e.Request));
                    }
                    break;
            }
        }

        public RegistrationState State { get; private set; }
        void registration_StateChanged(object sender, EventArgs e)
        {
            try
            {
                SIP_UA_Registration registration = (SIP_UA_Registration)sender;
                RegistrationEventArgs registrationEventArgs = createRegistrationEventArgs(registration);
                State = registrationEventArgs.State;

                if (State != RegistrationState.Registered && !mIsDispose)
                {
                    WaitSecond waitSecond = new WaitSecond(2, registrationEventArgs);
                    waitSecond.WaitEvent += new EventHandler(waitSecond_WaitEvent);
                }
                else
                {
                    if (this.RegisteringEvent != null)
                        this.RegisteringEvent.Invoke(this, registrationEventArgs);
                    if(registrationEventArgs.State == RegistrationState.Disposed && mIsDispose)
                        m_pStack.Dispose();
                }
            }
            catch (Exception error)
            {
                if (TelSipError != null) TelSipError(this, new StrErrorEventArgs("SipOperation registration_StateChanged", error, false));
            }
        }

        private RegistrationEventArgs createRegistrationEventArgs(SIP_UA_Registration registration)
        {
            RegistrationEventArgs registrationEventArgs = null;
            RegistrationState _state;
            Connected = registration.State == SIP_UA_RegistrationState.Registered;
            switch (registration.State)
            {
                case SIP_UA_RegistrationState.Registering:
                    _state = RegistrationState.Registering;
                    registrationEventArgs = new RegistrationEventArgs(_state, "соединение");
                    break;
                case SIP_UA_RegistrationState.Registered:
                    _state = RegistrationState.Registered;
                    m_ConnectorIP = registration.Flow.LocalEP.Address;
                    registrationEventArgs = new RegistrationEventArgs(_state, string.Format("соединен - {0}", registration.AOR));
                    break;
                case SIP_UA_RegistrationState.Unregistered:
                    _state = RegistrationState.Unregistered;
                    registrationEventArgs = new RegistrationEventArgs(_state, "разъединение");
                    break;
                case SIP_UA_RegistrationState.Error:
                    _state = RegistrationState.Error;
                    registrationEventArgs = new RegistrationEventArgs(_state, "ошибка");
                    break;
                case SIP_UA_RegistrationState.Disposed:
                    _state = RegistrationState.Disposed;
                    registrationEventArgs = new RegistrationEventArgs(_state, "разъединен");
                    Dispose();
                    break;
                default:
                    registrationEventArgs = new RegistrationEventArgs(State, "разъединен");
                    Dispose();
                    break;
            }
            return registrationEventArgs;
        }

        void waitSecond_WaitEvent(object sender, EventArgs e)
        {
            RegistrationEventArgs registrationEventArgs = sender as RegistrationEventArgs;
            if (registrationEventArgs != null && State != RegistrationState.Registered)
            {
                if (this.RegisteringEvent != null)
                    this.RegisteringEvent.Invoke(this, registrationEventArgs);
            }
        }

        #region Исходящий

        public bool AudioInCkek()
        {
            return AudioIn.Devices.Length > 0;
        }

        public static List<DriverView> AudioInList()
        {
            List<DriverView> res = (from q in AudioIn.Devices
                                    select new DriverView()
                                               {
                                                   Index = q.Index,
                                                   Name = q.Name
                                               }).ToList();
            return res;
        }

        public static List<DriverView> AudioOutList()
        {
            List<DriverView> res = (from q in AudioOut.Devices
                                    select new DriverView()
                                    {
                                        Index = q.Index,
                                        Name = q.Name
                                    }).ToList();
            return res;
        }

        public bool AudioOutCkek()
        {
            return AudioOut.Devices.Length > 0;
        }

        #endregion

       /// <summary>
        /// Gets local IPv4 address.
        /// </summary>
        /// <returns>Returns Ip address.</returns>
        /// <exception cref="Exception">Is raised when no suitable IP address found.</exception>
        public static IPAddress GetLocalIP()
        {
            foreach (IPAddress ip in System.Net.Dns.GetHostAddresses(""))
            {
                if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork && !ip.Equals(IPAddress.Loopback))
                {
                    return ip;
                }
            }

            throw new Exception("No suitable IPv4 address available.");
        }

        public event EventHandler<StringEventArgs> AnswerSaveStarting;


    }
}